﻿using System;
using System.Collections.Generic;
using System.Threading;
using TypeBuilderFx.Core;
using TypeBuilderFx.Core.Emitters;
using Simp.Model.CoreExpressions.Sequences;

namespace Simp.Model
{
	/// <summary>Contains methods helping to manipulate <see cref="IExpression"/>.</summary>
	public static class ExpressionHelper
	{
		#region Inner Types
		private enum SimplifiablePath
		{
			None,
			ImplementSimplifiable,
			Rules
		}

		private struct ExpresionTypeInfo
		{
			private static IDictionary<Type, ExpresionTypeInfo> _typeInfoIndex = new Dictionary<Type, ExpresionTypeInfo>();

			public Type Type { get; set; }
			public Exception CompletnessException { get; set; }
			public INamedExpression NamedExpression { get; set; }
			public IParameterPolicyExpression ParameterPolicyExpression { get; set; }
			public SimplifiablePath SimplifiablePath { get; set; }

			public static ExpresionTypeInfo GetTypeInfo(Type type)
			{
				ExpresionTypeInfo typeInfo;

				if (!_typeInfoIndex.TryGetValue(type, out typeInfo))
				{
					typeInfo = CreateTypeInfo(type);

					lock (_syncObject)
					{	//	Double check:  check if the type info still isn't there in the protected area
						IDictionary<Type, ExpresionTypeInfo> newTypeInfoIndex =
							new Dictionary<Type, ExpresionTypeInfo>(_typeInfoIndex);

						Interlocked.Exchange(ref _typeInfoIndex, newTypeInfoIndex);
					}
				}

				return typeInfo;
			}

			private static ExpresionTypeInfo CreateTypeInfo(Type type)
			{
				ExpresionTypeInfo typeInfo = new ExpresionTypeInfo { Type = type };

				if (!typeof(INamedExpression).IsAssignableFrom(type))
				{
					object[] attributes = type.GetCustomAttributes(typeof(ExpressionNameAttribute), true);

					if (attributes.Length == 0)
					{
						typeInfo.CompletnessException = new ArgumentException(string.Format(
							"Type '{0}' doesn't expose INamedExpression nor does it use ExpressionNameAttribute",
							type.Name));

						return typeInfo;
					}
					else
					{
						ExpressionNameAttribute nameAttribute = (ExpressionNameAttribute)attributes[0];

						typeInfo.NamedExpression = _namedExpressionFactory.Build(nameAttribute.Name);
					}
				}
				if (!typeof(IParameterPolicyExpression).IsAssignableFrom(type))
				{
					object[] attributes = type.GetCustomAttributes(typeof(ParameterPolicyAttribute), true);

					if (attributes.Length == 0)
					{
						typeInfo.CompletnessException = new ArgumentException(string.Format(
							"Type '{0}' doesn't expose IParameterPolicyExpression nor does it use ExpressionParameterPolicyAttribute",
							type.Name));

						return typeInfo;
					}
					else
					{
						ParameterPolicyAttribute parameterPolicyAttribute =
							(ParameterPolicyAttribute)attributes[0];

						typeInfo.ParameterPolicyExpression =
							_parameterPolicyFactory.Build(parameterPolicyAttribute.ParameterBindings);
					}
				}
				if (typeof(ISimplifiableExpression).IsAssignableFrom(type))
				{
					typeInfo.SimplifiablePath = SimplifiablePath.ImplementSimplifiable;
				}
				else
				{
					typeInfo.SimplifiablePath = SimplifiablePath.None;
				}

				return typeInfo;
			}
		}

		private class ExpressionProxy : IExpressionProxy
		{
			private readonly IExpression _innerExpression;
			private readonly INamedExpression _namedExpression;
			private readonly IParameterPolicyExpression _parameterPolicyExpression;
			private readonly ISimplifiableExpression _simplifiableExpression;

			public ExpressionProxy(
				IExpression innerExpression,
				INamedExpression namedExpression,
				IParameterPolicyExpression parameterPolicyExpression,
				ISimplifiableExpression simplifiableExpression)
			{
				_innerExpression = innerExpression;
				_namedExpression = namedExpression;
				_parameterPolicyExpression = parameterPolicyExpression;
				_simplifiableExpression = simplifiableExpression;
			}

			#region IExpressionProxy Members
			IExpression IExpressionProxy.InnerExpression
			{
				get { return _innerExpression; }
			}
			#endregion

			#region INamedExpression Members
			string INamedExpression.Name
			{
				get { return _namedExpression.Name; }
			}
			#endregion

			#region IParameterPolicyExpression Members
			IParameterBinding[] IParameterPolicyExpression.ParameterBindings
			{
				get { return _parameterPolicyExpression.ParameterBindings; }
			}
			#endregion

			#region ISimplifiableExpression Members
			IExpression ISimplifiableExpression.Simplify(
				SimplificationMode simplificationMode,
				params IExpression[] parameterList)
			{
				if (_simplifiableExpression == null)
				{
					return null;
				}
				else
				{
					return _simplifiableExpression.Simplify(simplificationMode, parameterList);
				}
			}
			#endregion
		}
		#endregion

		private static readonly IFactory<INamedExpression, string> _namedExpressionFactory =
			(IFactory<INamedExpression, string>)ImmutablePropertyHolderEmitter.GetFactory<INamedExpression>();
		private static readonly IFactory<IParameterPolicyExpression, IParameterBinding[]> _parameterPolicyFactory =
			(IFactory<IParameterPolicyExpression, IParameterBinding[]>)ImmutablePropertyHolderEmitter.GetFactory<IParameterPolicyExpression>();
		private static readonly object _syncObject = new object();

		/// <summary>Factory of <see cref="IFunctionParameterBinding"/>.</summary>
		public static readonly IFactory<IParameterBinding, string, IExpression> ParameterBindingFactory =
			(IFactory<IParameterBinding, string, IExpression>)ImmutablePropertyHolderEmitter.GetFactory<IParameterBinding>();

		/// <summary>Empty binding set.</summary>
		public static readonly IParameterBinding[] EmptyBindings = new IParameterBinding[0];

		/// <summary>Empty expression list.</summary>
		public static readonly IExpression[] EmptyExpressionList = new IExpression[0];

		/// <summary>Creates a list of parameter bindings with no default values.</summary>
		/// <param name="parameterNameList"></param>
		/// <returns></returns>
		public static IParameterBinding[] CreateParameterBinding(params string[] parameterNameList)
		{
			if (parameterNameList == null)
			{
				throw new ArgumentNullException("parameterNameList");
			}

			IParameterBinding[] bindingList = new IParameterBinding[parameterNameList.Length];

			for (int i = 0; i != bindingList.Length; ++i)
			{
				bindingList[i] = ParameterBindingFactory.Build(parameterNameList[i], null);
			}

			return bindingList;
		}

		/// <summary>Creates a list of parameter bindings with no default values.</summary>
		/// <param name="parameterCount"></param>
		/// <returns></returns>
		public static IParameterBinding[] CreateParameterBinding(int parameterCount)
		{
			if (parameterCount < 0)
			{
				throw new ArgumentException("Can't be negative", "parameterCount");
			}

			IParameterBinding[] bindingList = new IParameterBinding[parameterCount];

			for (int i = 0; i != bindingList.Length; ++i)
			{
				bindingList[i] = ParameterBindingFactory.Build(string.Concat("p", i), null);
			}

			return bindingList;
		}

		/// <summary>Returns a proxy to an expression, exposing all useful interfaces.</summary>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static IExpressionProxy GetProxy(IExpression expression)
		{
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}

			ExpresionTypeInfo typeInfo = ExpresionTypeInfo.GetTypeInfo(expression.GetType());

			if (typeInfo.CompletnessException != null)
			{
				throw typeInfo.CompletnessException;
			}
			else
			{
				INamedExpression namedExpression = typeInfo.NamedExpression == null
					? (INamedExpression)expression
					: typeInfo.NamedExpression;
				IParameterPolicyExpression parameterPolicyExpression = typeInfo.NamedExpression == null
					? (IParameterPolicyExpression)expression
					: typeInfo.ParameterPolicyExpression;
				ISimplifiableExpression simplifiableExpression =
					typeInfo.SimplifiablePath == SimplifiablePath.ImplementSimplifiable
					? (ISimplifiableExpression)expression
					: null;

				return new ExpressionProxy(expression, namedExpression, parameterPolicyExpression, simplifiableExpression);
			}
		}

		/// <summary>Returns a default value if the value is <c>null</c>.</summary>
		/// <param name="value"></param>
		/// <param name="defaultValue"></param>
		/// <returns></returns>
		public static T DefaultIfNull<T>(T value, T defaultValue)
			where T : class
		{
			return value == null ? defaultValue : value;
		}

		/// <summary>Simplifies the expression if it supports simplification.</summary>
		/// <param name="isReturningNull"/>
		/// <param name="expression"></param>
		/// <param name="simplificationMode"/>
		/// <param name="parameterList"></param>
		/// <returns></returns>
		public static IExpression Simplify(
			bool isReturningNull,
			IExpression expression,
			SimplificationMode simplificationMode,
			params IExpression[] parameterList)
		{
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}
			if (parameterList == null)
			{
				throw new ArgumentNullException("parameterList");
			}
			IExpressionProxy proxy = GetProxy(expression);
			IExpression simplifiedExpression = proxy.Simplify(simplificationMode, parameterList);

			return !isReturningNull && simplifiedExpression == null ? expression : simplifiedExpression;
		}

		/// <summary>Transforms the expression applying a sequence of transformations in order.</summary>
		/// <param name="isReturningNull"></param>
		/// <param name="expression"></param>
		/// <param name="transformationList"></param>
		/// <returns></returns>
		public static IExpression Tranform(
			bool isReturningNull,
			IExpression expression,
			params IExpressionTransformation[] transformationList)
		{
			if (transformationList == null)
			{
				throw new ArgumentNullException("transformationList");
			}
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}

			bool hasChanged = false;

			foreach (IExpressionTransformation transform in transformationList)
			{
				IExpression newExpression = transform.Tranform(expression);

				if (newExpression != null)
				{
					hasChanged = true;
				}
				expression = DefaultIfNull(newExpression, expression);
			}

			return hasChanged || !isReturningNull ? expression : null;
		}

		/// <summary>Transforms a list of expressions.</summary>
		/// <param name="isReturningNull"></param>
		/// <param name="transformation"></param>
		/// <param name="expressionList"></param>
		/// <returns></returns>
		public static IExpression[] Tranform(
			bool isReturningNull,
			IExpressionTransformation transformation,
			IExpression[] expressionList)
		{
			if (transformation == null)
			{
				throw new ArgumentNullException("transformation");
			}
			if (expressionList == null)
			{
				throw new ArgumentNullException("expressionList");
			}

			bool hasChanged = false;
			IExpression[] newExpressionList = new IExpression[expressionList.Length];

			for (int i = 0; i != expressionList.Length; ++i)
			{
				IExpression transformedExpression = transformation.Tranform(expressionList[i]);

				if (transformedExpression == null)
				{
					newExpressionList[i] = expressionList[i];
				}
				else
				{
					hasChanged = true;
					newExpressionList[i] = transformedExpression;
				}
			}

			return !hasChanged && isReturningNull ? null : newExpressionList;
		}

		/// <summary>Transforms a composite expression.</summary>
		/// <param name="isReturningNull"></param>
		/// <param name="transformation"></param>
		/// <param name="composite"></param>
		/// <returns></returns>
		public static CompositeExpression TranformComposite(
			bool isReturningNull,
			IExpressionTransformation transformation,
			CompositeExpression composite)
		{
			if (transformation == null)
			{
				throw new ArgumentNullException("transformation");
			}
			if (composite == null)
			{
				throw new ArgumentNullException("composite");
			}

			IExpression[] transformedParameterList =
				Tranform(true, transformation, composite.ParameterList);
			IExpression transformedInnerExpression =
				transformation.Tranform(composite.InnerExpression);

			if (transformedInnerExpression == null && transformedParameterList == null)
			{
				return isReturningNull ? null : composite;
			}
			else
			{
				return new CompositeExpression(
					ExpressionHelper.DefaultIfNull(transformedInnerExpression, composite.InnerExpression),
					ExpressionHelper.DefaultIfNull(transformedParameterList, composite.ParameterList));
			}
		}

		/// <summary>Serializes an expression if possible.</summary>
		/// <param name="expression"></param>
		/// <param name="parameters"></param>
		/// <returns/>
		public static string Serialize(IExpression expression, params IExpression[] parameters)
		{
			AppendOnlyStringBuilder builder = new AppendOnlyStringBuilder();

			Serialize(builder, expression, parameters);

			return builder.ToString();
		}

		/// <summary>Serializes an expression if possible.</summary>
		/// <param name="builder"></param>
		/// <param name="expression"></param>
		/// <param name="parameters"></param>
		public static void Serialize(AppendOnlyStringBuilder builder, IExpression expression, params IExpression[] parameters)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}

			IExpressionSerializer serializer = expression as IExpressionSerializer;

			if (serializer != null)
			{
				serializer.Serialize(builder, parameters);
			}
			else
			{	//	Unsupported serialization
				builder.Append("<Unsupported : ");
				builder.Append(GetProxy(expression).Name);
				builder.Append(">");
			}
		}

		/// <summary>Generic serialization of an expression.</summary>
		/// <param name="builder"></param>
		/// <param name="expression"></param>
		/// <param name="parameters"></param>
		public static void SerializeGeneric(
			AppendOnlyStringBuilder builder,
			IExpression expression,
			params IExpression[] parameters)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}

			builder.Append(GetProxy(expression).Name);
			if (parameters.Length > 0)
			{
				builder.Append("(");
				for (int i = 0; i != parameters.Length; ++i)
				{
					IExpression param = parameters[i];

					Serialize(builder, param);
					if (i != parameters.Length - 1)
					{
						builder.Append(", ");
					}
				}
				builder.Append(")");
			}
		}

		/// <summary>
		/// Checks if the given expression is a composite expression with the given expression type as inner expression.
		/// </summary>
		/// <typeparam name="FT"/>
		/// <param name="expression"/>
		/// <returns></returns>
		public static CompositeExpression TryGetComposite<FT>(IExpression expression)
			where FT : class, IExpression
		{
			FT innerExpression;

			return TryGetComposite(expression, out innerExpression);
		}

		/// <summary>
		/// Checks if the given expression is a composite expression with the given expression type as inner expression.
		/// </summary>
		/// <typeparam name="FT"></typeparam>
		/// <param name="expression"></param>
		/// <param name="innerExpression"></param>
		/// <returns></returns>
		public static CompositeExpression TryGetComposite<FT>(IExpression expression, out FT innerExpression)
			where FT : class, IExpression
		{
			CompositeExpression compositeExpression = expression as CompositeExpression;

			if (compositeExpression == null)
			{
				innerExpression = expression as FT;

				if (innerExpression == null)
				{
					return null;
				}
				else
				{	//	Fake a composite expression owning no parameters
					return new CompositeExpression(innerExpression);
				}
			}
			else
			{
				innerExpression = compositeExpression.InnerExpression as FT;

				if (innerExpression == null)
				{
					return null;
				}
				else
				{
					return compositeExpression;
				}
			}
		}

		/// <summary>Returns a <see cref="ISequenceAccessor"/> to the underlying sequence if any (otherwise returns <c>null</c>).</summary>
		/// <remarks>Takes care of the logic of passing through composite expressions.</remarks>
		/// <param name="expression"/>
		/// <returns></returns>
		public static ISequenceAccessor GetAccessor(IExpression expression)
		{
			ISequenceExpression sequence;
			CompositeExpression composite = TryGetComposite(expression, out sequence);

			if (composite == null)
			{
				return null;
			}
			else
			{
				return sequence.GetAccessor(composite.ParameterList);
			}
		}
	}
}